﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;

namespace Fwql.Net.Extensions
{
    public static class ListExtension
    {
        /// <summary>
        /// 	Return the index of the first matching item or -1.
        /// </summary>
        /// <typeparam name = "T"></typeparam>
        /// <param name = "list">The list.</param>
        /// <param name = "comparison">The comparison.</param>
        /// <returns>The item index</returns>
        public static int IndexOf<T>(this IList<T> list, Func<T, bool> comparison)
        {
            for (var i = 0; i < list.Count; i++)
            {
                if (comparison(list[i]))
                    return i;
            }
            return -1;
        }

        /// <summary>
        /// 	Using Relugar Expression, find the top matches for each item in the source specified by the arguments to search.
        /// </summary>
        /// <param name = "list">
        /// 	The source.
        /// </param>
        /// <param name = "searchString">
        /// 	The search string.
        /// </param>
        /// <param name = "top">
        /// 	The top.
        /// </param>
        /// <param name = "args">
        /// 	The args.
        /// </param>
        /// <typeparam name = "T">
        /// </typeparam>
        /// <returns>
        /// 	A List of top matches.
        /// </returns>
        public static List<T> Match<T>(this IList<T> list,
                                       string searchString,
                                       int top,
                                       params Expression<Func<T, object>>[] args)
        {
            // Create a new list of results and matches;
            var results = new List<T>();
            var matches = new Dictionary<T, int>();
            var maxMatch = 0;
            // For each item in the source
            list.ForEach(s =>
                         {
                             // Generate the expression string from the argument.
                             var regExp = string.Empty;
                             if (args != null)
                             {
                                 // For each argument
                                 Array.ForEach(args,
                                               a =>
                                               {
                                                   // Compile the expression
                                                   var property = a.Compile();
                                                   // Attach the new property to the expression string
                                                   regExp += (string.IsNullOrEmpty(regExp) ? "(?:" : "|(?:") +
                                                             property(s) + ")+?";
                                               });
                             }
                             // Get the matches
                             var match = Regex.Matches(searchString, regExp, RegexOptions.IgnoreCase);
                             // If there are more than one match
                             if (match.Count > 0)
                             {
                                 // Add it to the match dictionary, including the match count.
                                 matches.Add(s, match.Count);
                             }
                             // Get the highest max matching
                             maxMatch = match.Count > maxMatch ? match.Count : maxMatch;
                         });
            // Convert the match dictionary into a list
            var matchList = matches.ToList();

            // Sort the list by decending match counts
            // matchList.Sort((s1, s2) => s2.Value.CompareTo(s1.Value));

            // Remove all matches that is less than the best match.
            matchList.RemoveAll(s => s.Value < maxMatch);

            // If the top value is set and is less than the number of matches
            var getTop = top > 0 && top < matchList.Count ? top : matchList.Count;

            // Add the maches into the result list.
            for (var i = 0; i < getTop; i++)
                results.Add(matchList[i].Key);

            return results;
        }

        #region Merge

        /// <summary>The merge.</summary>
        /// <param name="lists">The lists.</param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static List<T> Merge<T>(params List<T>[] lists)
        {
            var merged = new List<T>();
            foreach (var list in lists)
                merged.Merge(list);
            return merged;
        }

        /// <summary>The merge.</summary>
        /// <param name="match">The match.</param>
        /// <param name="lists">The lists.</param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static List<T> Merge<T>(Expression<Func<T, object>> match, params List<T>[] lists)
        {
            var merged = new List<T>();
            foreach (var list in lists)
                merged.Merge(list, match);
            return merged;
        }

        /// <summary>The merge.</summary>
        /// <param name="list1">The list 1.</param>
        /// <param name="list2">The list 2.</param>
        /// <param name="match">The match.</param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static List<T> Merge<T>(this List<T> list1, List<T> list2, Expression<Func<T, object>> match)
        {
            if (list1 != null && list2 != null && match != null)
            {
                var matchFunc = match.Compile();
                foreach (var item in list2)
                {
                    var key = matchFunc(item);
                    if (!list1.Exists(i => matchFunc(i).Equals(key)))
                        list1.Add(item);
                }
            }

            return list1;
        }

        /// <summary>The merge.</summary>
        /// <param name="list1">The list 1.</param>
        /// <param name="list2">The list 2.</param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static List<T> Merge<T>(this List<T> list1, List<T> list2)
        {
            if (list1 != null && list2 != null)
                foreach (var item in list2.Where(item => !list1.Contains(item)))
                    list1.Add(item);
            return list1;
        }

        #endregion

        /*///<summary>
        ///	Cast this list into a List
        ///</summary>
        ///<param name = "source"></param>
        ///<typeparam name = "T"></typeparam>
        ///<returns></returns>
        public static List<T> Cast<T>(this IList source)
        {
            var list = new List<T>();
            list.AddRange(source.OfType<T>());
            return list;
        }*/
    }
}